/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) Module
 * -----------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Compiler test case.
 *//*--------------------------------------------------------------------*/

#include "glsShaderLibraryCase.hpp"

#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuSurface.hpp"

#include "tcuStringTemplate.hpp"
#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluDrawUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluStrUtil.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include "deRandom.hpp"
#include "deInt32.h"
#include "deMath.h"
#include "deString.h"
#include "deStringUtil.hpp"
#include "deSharedPtr.hpp"

#include <map>
#include <vector>
#include <string>
#include <sstream>

namespace deqp
{
namespace gls
{

using namespace tcu;
using namespace glu;
using namespace glu::sl;

using std::vector;
using std::string;
using std::ostringstream;
using std::map;
using std::pair;

using de::SharedPtr;

// OpenGL-specific specialization utils

static vector<RequiredExtension> checkAndSpecializeExtensions (const vector<RequiredExtension>&	src,
															   const ContextInfo&				ctxInfo)
{
	vector<RequiredExtension>	specialized;

	for (size_t extNdx = 0; extNdx < src.size(); ++extNdx)
	{
		const RequiredExtension&	extension		= src[extNdx];
		int							supportedAltNdx	= -1;

		for (size_t alternativeNdx = 0; alternativeNdx < extension.alternatives.size(); ++alternativeNdx)
		{
			if (ctxInfo.isExtensionSupported(extension.alternatives[alternativeNdx].c_str()))
			{
				supportedAltNdx	= (int)alternativeNdx;
				break;
			}
		}

		if (supportedAltNdx >= 0)
		{
			specialized.push_back(RequiredExtension(extension.alternatives[supportedAltNdx], extension.effectiveStages));
		}
		else
		{
			// no extension(s). Make a nice output
			std::ostringstream extensionList;

			for (size_t ndx = 0; ndx < extension.alternatives.size(); ++ndx)
			{
				if (!extensionList.str().empty())
					extensionList << ", ";
				extensionList << extension.alternatives[ndx];
			}

			if (extension.alternatives.size() == 1)
				throw tcu::NotSupportedError("Test requires extension " + extensionList.str());
			else
				throw tcu::NotSupportedError("Test requires any extension of " + extensionList.str());
		}
	}

	return specialized;
}

static void checkImplementationLimits (const vector<RequiredCapability>&	requiredCaps,
									   const ContextInfo&					ctxInfo)
{
	for (size_t capNdx = 0; capNdx < requiredCaps.size(); ++capNdx)
	{
		const RequiredCapability& capability = requiredCaps[capNdx];
		if (capability.type != CAPABILITY_LIMIT)
			continue;

		const deUint32	pname			= capability.enumName;
		const int		requiredValue	= capability.referenceValue;
		const int		supportedValue	= ctxInfo.getInt((int)pname);

		if (supportedValue <= requiredValue)
			throw tcu::NotSupportedError("Test requires " + de::toString(glu::getGettableStateStr(pname)) + " (" + de::toString(supportedValue) + ") >= " + de::toString(requiredValue));
	}
}

// Shader source specialization

// This functions builds a matching vertex shader for a 'both' case, when
// the fragment shader is being tested.
// We need to build attributes and varyings for each 'input'.
static string genVertexShader (const ShaderCaseSpecification& spec)
{
	ostringstream		res;
	const bool			usesInout	= glslVersionUsesInOutQualifiers(spec.targetVersion);
	const char* const	vtxIn		= usesInout ? "in"	: "attribute";
	const char* const	vtxOut		= usesInout ? "out"	: "varying";

	res << glu::getGLSLVersionDeclaration(spec.targetVersion) << "\n";

	// Declarations (position + attribute/varying for each input).
	res << "precision highp float;\n";
	res << "precision highp int;\n";
	res << "\n";
	res << vtxIn << " highp vec4 dEQP_Position;\n";

	for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++)
	{
		const Value&		val			= spec.values.inputs[ndx];
		const DataType		basicType	= val.type.getBasicType();
		const DataType		floatType	= getDataTypeFloatScalars(basicType);
		const char* const	typeStr		= getDataTypeName(floatType);

		res << vtxIn << " " << typeStr << " a_" << val.name << ";\n";

		if (getDataTypeScalarType(basicType) == TYPE_FLOAT)
			res << vtxOut << " " << typeStr << " " << val.name << ";\n";
		else
			res << vtxOut << " " << typeStr << " v_" << val.name << ";\n";
	}
	res << "\n";

	// Main function.
	// - gl_Position = dEQP_Position;
	// - for each input: write attribute directly to varying
	res << "void main()\n";
	res << "{\n";
	res << "	gl_Position = dEQP_Position;\n";
	for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++)
	{
		const Value&	val		= spec.values.inputs[ndx];
		const string&	name	= val.name;

		if (getDataTypeScalarType(val.type.getBasicType()) == TYPE_FLOAT)
			res << "	" << name << " = a_" << name << ";\n";
		else
			res << "	v_" << name << " = a_" << name << ";\n";
	}

	res << "}\n";
	return res.str();
}

static void genCompareOp (ostringstream& output, const char* dstVec4Var, const ValueBlock& valueBlock, const char* nonFloatNamePrefix, const char* checkVarName)
{
	bool isFirstOutput = true;

	for (size_t ndx = 0; ndx < valueBlock.outputs.size(); ndx++)
	{
		const Value&	val		= valueBlock.outputs[ndx];

		// Check if we're only interested in one variable (then skip if not the right one).
		if (checkVarName && val.name != checkVarName)
			continue;

		// Prefix.
		if (isFirstOutput)
		{
			output << "bool RES = ";
			isFirstOutput = false;
		}
		else
			output << "RES = RES && ";

		// Generate actual comparison.
		if (getDataTypeScalarType(val.type.getBasicType()) == TYPE_FLOAT)
			output << "isOk(" << val.name << ", ref_" << val.name << ", 0.05);\n";
		else
			output << "isOk(" << nonFloatNamePrefix << val.name << ", ref_" << val.name << ");\n";
	}

	if (isFirstOutput)
		output << dstVec4Var << " = vec4(1.0);\n";	// \todo [petri] Should we give warning if not expect-failure case?
	else
		output << dstVec4Var << " = vec4(RES, RES, RES, 1.0);\n";
}

static inline bool supportsFragmentHighp (glu::GLSLVersion version)
{
	return version != glu::GLSL_VERSION_100_ES;
}

static string genFragmentShader (const ShaderCaseSpecification& spec)
{
	ostringstream		shader;
	const bool			usesInout		= glslVersionUsesInOutQualifiers(spec.targetVersion);
	const bool			customColorOut	= usesInout;
	const char*	const	fragIn			= usesInout ? "in" : "varying";
	const char*	const	prec			= supportsFragmentHighp(spec.targetVersion) ? "highp" : "mediump";

	shader << glu::getGLSLVersionDeclaration(spec.targetVersion) << "\n";

	shader << "precision " << prec << " float;\n";
	shader << "precision " << prec << " int;\n";
	shader << "\n";

	if (customColorOut)
	{
		shader << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
		shader << "\n";
	}

	genCompareFunctions(shader, spec.values, true);
	shader << "\n";

	// Declarations (varying, reference for each output).
	for (size_t ndx = 0; ndx < spec.values.outputs.size(); ndx++)
	{
		const Value&		val				= spec.values.outputs[ndx];
		const DataType		basicType		= val.type.getBasicType();
		const DataType		floatType		= getDataTypeFloatScalars(basicType);
		const char* const	floatTypeStr	= getDataTypeName(floatType);
		const char* const	refTypeStr		= getDataTypeName(basicType);

		if (getDataTypeScalarType(basicType) == TYPE_FLOAT)
			shader << fragIn << " " << floatTypeStr << " " << val.name << ";\n";
		else
			shader << fragIn << " " << floatTypeStr << " v_" << val.name << ";\n";

		shader << "uniform " << refTypeStr << " ref_" << val.name << ";\n";
	}

	shader << "\n";
	shader << "void main()\n";
	shader << "{\n";

	shader << "	";
	genCompareOp(shader, customColorOut ? "dEQP_FragColor" : "gl_FragColor", spec.values, "v_", DE_NULL);

	shader << "}\n";
	return shader.str();
}

// Specialize a shader for the vertex shader test case.
static string specializeVertexShader (const ShaderCaseSpecification& spec, const std::string& src, const vector<RequiredExtension>& extensions)
{
	ostringstream		decl;
	ostringstream		setup;
	ostringstream		output;
	const bool			usesInout	= glslVersionUsesInOutQualifiers(spec.targetVersion);
	const char* const	vtxIn		= usesInout ? "in"	: "attribute";
	const char* const	vtxOut		= usesInout ? "out"	: "varying";

	// generated from "both" case
	DE_ASSERT(spec.caseType == CASETYPE_VERTEX_ONLY);

	// Output (write out position).
	output << "gl_Position = dEQP_Position;\n";

	// Declarations (position + attribute for each input, varying for each output).
	decl << vtxIn << " highp vec4 dEQP_Position;\n";

	for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++)
	{
		const Value&		val				= spec.values.inputs[ndx];
		const DataType		basicType		= val.type.getBasicType();
		const DataType		floatType		= getDataTypeFloatScalars(basicType);
		const char* const	floatTypeStr	= getDataTypeName(floatType);
		const char* const	refTypeStr		= getDataTypeName(basicType);

		if (getDataTypeScalarType(basicType) == TYPE_FLOAT)
		{
			decl << vtxIn << " " << floatTypeStr << " " << val.name << ";\n";
		}
		else
		{
			decl << vtxIn << " " << floatTypeStr << " a_" << val.name << ";\n";
			setup << refTypeStr << " " << val.name << " = " << refTypeStr << "(a_" << val.name << ");\n";
		}
	}

	// \todo [2015-07-24 pyry] Why are uniforms missing?

	for (size_t ndx = 0; ndx < spec.values.outputs.size(); ndx++)
	{
		const Value&		val				= spec.values.outputs[ndx];
		const DataType		basicType		= val.type.getBasicType();
		const DataType		floatType		= getDataTypeFloatScalars(basicType);
		const char* const	floatTypeStr	= getDataTypeName(floatType);
		const char* const	refTypeStr		= getDataTypeName(basicType);

		if (getDataTypeScalarType(basicType) == TYPE_FLOAT)
			decl << vtxOut << " " << floatTypeStr << " " << val.name << ";\n";
		else
		{
			decl << vtxOut << " " << floatTypeStr << " v_" << val.name << ";\n";
			decl << refTypeStr << " " << val.name << ";\n";

			output << "v_" << val.name << " = " << floatTypeStr << "(" << val.name << ");\n";
		}
	}

	// Shader specialization.
	map<string, string> params;
	params.insert(pair<string, string>("DECLARATIONS", decl.str()));
	params.insert(pair<string, string>("SETUP", setup.str()));
	params.insert(pair<string, string>("OUTPUT", output.str()));
	params.insert(pair<string, string>("POSITION_FRAG_COLOR", "gl_Position"));

	StringTemplate	tmpl	(src);
	const string	baseSrc	= tmpl.specialize(params);
	const string	withExt	= injectExtensionRequirements(baseSrc, extensions, SHADERTYPE_VERTEX);

	return withExt;
}

// Specialize a shader for the fragment shader test case.
static string specializeFragmentShader (const ShaderCaseSpecification& spec, const std::string& src, const vector<RequiredExtension>& extensions)
{
	ostringstream		decl;
	ostringstream		setup;
	ostringstream		output;

	const bool			usesInout		= glslVersionUsesInOutQualifiers(spec.targetVersion);
	const bool			customColorOut	= usesInout;
	const char* const	fragIn			= usesInout			? "in"				: "varying";
	const char* const	fragColor		= customColorOut	? "dEQP_FragColor"	: "gl_FragColor";

	// generated from "both" case
	DE_ASSERT(spec.caseType == CASETYPE_FRAGMENT_ONLY);

	genCompareFunctions(decl, spec.values, false);
	genCompareOp(output, fragColor, spec.values, "", DE_NULL);

	if (customColorOut)
		decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";

	for (size_t ndx = 0; ndx < spec.values.inputs.size(); ndx++)
	{
		const Value&		val				= spec.values.inputs[ndx];
		const DataType		basicType		= val.type.getBasicType();
		const DataType		floatType		= getDataTypeFloatScalars(basicType);
		const char* const	floatTypeStr	= getDataTypeName(floatType);
		const char* const	refTypeStr		= getDataTypeName(basicType);

		if (getDataTypeScalarType(basicType) == TYPE_FLOAT)
			decl << fragIn << " " << floatTypeStr << " " << val.name << ";\n";
		else
		{
			decl << fragIn << " " << floatTypeStr << " v_" << val.name << ";\n";
			std::string offset = isDataTypeIntOrIVec(basicType) ? " * 1.0025" : ""; // \todo [petri] bit of a hack to avoid errors in chop() due to varying interpolation
			setup << refTypeStr << " " << val.name << " = " << refTypeStr << "(v_" << val.name << offset << ");\n";
		}
	}

	// \todo [2015-07-24 pyry] Why are uniforms missing?

	for (size_t ndx = 0; ndx < spec.values.outputs.size(); ndx++)
	{
		const Value&		val				= spec.values.outputs[ndx];
		const DataType		basicType		= val.type.getBasicType();
		const char* const	refTypeStr		= getDataTypeName(basicType);

		decl << "uniform " << refTypeStr << " ref_" << val.name << ";\n";
		decl << refTypeStr << " " << val.name << ";\n";
	}

	/* \todo [2010-04-01 petri] Check all outputs. */

	// Shader specialization.
	map<string, string> params;
	params.insert(pair<string, string>("DECLARATIONS", decl.str()));
	params.insert(pair<string, string>("SETUP", setup.str()));
	params.insert(pair<string, string>("OUTPUT", output.str()));
	params.insert(pair<string, string>("POSITION_FRAG_COLOR", fragColor));

	StringTemplate	tmpl	(src);
	const string	baseSrc	= tmpl.specialize(params);
	const string	withExt	= injectExtensionRequirements(baseSrc, extensions, SHADERTYPE_FRAGMENT);

	return withExt;
}

static void generateUniformDeclarations (std::ostream& dst, const ValueBlock& valueBlock)
{
	for (size_t ndx = 0; ndx < valueBlock.uniforms.size(); ndx++)
	{
		const Value&		val		= valueBlock.uniforms[ndx];
		const char* const	typeStr	= getDataTypeName(val.type.getBasicType());

		if (val.name.find('.') == string::npos)
			dst << "uniform " << typeStr << " " << val.name << ";\n";
	}
}

static map<string, string> generateVertexSpecialization (const ProgramSpecializationParams& specParams)
{
	const bool				usesInout	= glslVersionUsesInOutQualifiers(specParams.caseSpec.targetVersion);
	const char*				vtxIn		= usesInout ? "in" : "attribute";
	ostringstream			decl;
	ostringstream			setup;
	map<string, string>		params;

	decl << vtxIn << " highp vec4 dEQP_Position;\n";

	for (size_t ndx = 0; ndx < specParams.caseSpec.values.inputs.size(); ndx++)
	{
		const Value&		val			= specParams.caseSpec.values.inputs[ndx];
		const DataType		basicType	= val.type.getBasicType();
		const char* const	typeStr		= getDataTypeName(val.type.getBasicType());

		if (getDataTypeScalarType(basicType) == TYPE_FLOAT)
		{
			decl << vtxIn << " " << typeStr << " " << val.name << ";\n";
		}
		else
		{
			const DataType		floatType		= getDataTypeFloatScalars(basicType);
			const char* const	floatTypeStr	= getDataTypeName(floatType);

			decl << vtxIn << " " << floatTypeStr << " a_" << val.name << ";\n";
			setup << typeStr << " " << val.name << " = " << typeStr << "(a_" << val.name << ");\n";
		}
	}

	generateUniformDeclarations(decl, specParams.caseSpec.values);

	params.insert(pair<string, string>("VERTEX_DECLARATIONS",	decl.str()));
	params.insert(pair<string, string>("VERTEX_SETUP",			setup.str()));
	params.insert(pair<string, string>("VERTEX_OUTPUT",			string("gl_Position = dEQP_Position;\n")));

	return params;
}

static map<string, string> generateFragmentSpecialization (const ProgramSpecializationParams& specParams)
{
	const bool			usesInout		= glslVersionUsesInOutQualifiers(specParams.caseSpec.targetVersion);
	const bool			customColorOut	= usesInout;
	const char* const	fragColor		= customColorOut ? "dEQP_FragColor"	: "gl_FragColor";
	ostringstream		decl;
	ostringstream		output;
	map<string, string>	params;

	genCompareFunctions(decl, specParams.caseSpec.values, false);
	genCompareOp(output, fragColor, specParams.caseSpec.values, "", DE_NULL);

	if (customColorOut)
		decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";

	for (size_t ndx = 0; ndx < specParams.caseSpec.values.outputs.size(); ndx++)
	{
		const Value&		val			= specParams.caseSpec.values.outputs[ndx];
		const char*	const	refTypeStr	= getDataTypeName(val.type.getBasicType());

		decl << "uniform " << refTypeStr << " ref_" << val.name << ";\n";
		decl << refTypeStr << " " << val.name << ";\n";
	}

	generateUniformDeclarations(decl, specParams.caseSpec.values);

	params.insert(pair<string, string>("FRAGMENT_DECLARATIONS",	decl.str()));
	params.insert(pair<string, string>("FRAGMENT_OUTPUT",		output.str()));
	params.insert(pair<string, string>("FRAG_COLOR",			fragColor));

	return params;
}

static map<string, string> generateGeometrySpecialization (const ProgramSpecializationParams& specParams)
{
	ostringstream		decl;
	map<string, string>	params;

	decl << "layout (triangles) in;\n";
	decl << "layout (triangle_strip, max_vertices=3) out;\n";
	decl << "\n";

	generateUniformDeclarations(decl, specParams.caseSpec.values);

	params.insert(pair<string, string>("GEOMETRY_DECLARATIONS",		decl.str()));

	return params;
}

static map<string, string> generateTessControlSpecialization (const ProgramSpecializationParams& specParams)
{
	ostringstream		decl;
	ostringstream		output;
	map<string, string>	params;

	decl << "layout (vertices=3) out;\n";
	decl << "\n";

	generateUniformDeclarations(decl, specParams.caseSpec.values);

	output <<	"gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
				"gl_TessLevelInner[0] = 2.0;\n"
				"gl_TessLevelInner[1] = 2.0;\n"
				"gl_TessLevelOuter[0] = 2.0;\n"
				"gl_TessLevelOuter[1] = 2.0;\n"
				"gl_TessLevelOuter[2] = 2.0;\n"
				"gl_TessLevelOuter[3] = 2.0;";

	params.insert(pair<string, string>("TESSELLATION_CONTROL_DECLARATIONS",	decl.str()));
	params.insert(pair<string, string>("TESSELLATION_CONTROL_OUTPUT",		output.str()));
	params.insert(pair<string, string>("GL_MAX_PATCH_VERTICES",				de::toString(specParams.maxPatchVertices)));

	return params;
}

static map<string, string> generateTessEvalSpecialization (const ProgramSpecializationParams& specParams)
{
	ostringstream		decl;
	ostringstream		output;
	map<string, string>	params;

	decl << "layout (triangles) in;\n";
	decl << "\n";

	generateUniformDeclarations(decl, specParams.caseSpec.values);

	output <<	"gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position + gl_TessCoord[1] * gl_in[1].gl_Position + gl_TessCoord[2] * gl_in[2].gl_Position;\n";

	params.insert(pair<string, string>("TESSELLATION_EVALUATION_DECLARATIONS",	decl.str()));
	params.insert(pair<string, string>("TESSELLATION_EVALUATION_OUTPUT",		output.str()));
	params.insert(pair<string, string>("GL_MAX_PATCH_VERTICES",					de::toString(specParams.maxPatchVertices)));

	return params;
}

static void specializeShaderSources (ProgramSources&					dst,
									 const ProgramSources&				src,
									 const ProgramSpecializationParams&	specParams,
									 glu::ShaderType					shaderType,
									 map<string, string>				(*specializationGenerator) (const ProgramSpecializationParams& specParams))
{
	if (!src.sources[shaderType].empty())
	{
		const map<string, string>	tmplParams	= specializationGenerator(specParams);

		for (size_t ndx = 0; ndx < src.sources[shaderType].size(); ++ndx)
		{
			const StringTemplate	tmpl			(src.sources[shaderType][ndx]);
			const std::string		baseGLSLCode	= tmpl.specialize(tmplParams);
			const std::string		sourceWithExts	= injectExtensionRequirements(baseGLSLCode, specParams.requiredExtensions, shaderType);

			dst << glu::ShaderSource(shaderType, sourceWithExts);
		}
	}
}

static void specializeProgramSources (glu::ProgramSources&					dst,
									  const glu::ProgramSources&			src,
									  const ProgramSpecializationParams&	specParams)
{
	specializeShaderSources(dst, src, specParams, SHADERTYPE_VERTEX,					generateVertexSpecialization);
	specializeShaderSources(dst, src, specParams, SHADERTYPE_FRAGMENT,					generateFragmentSpecialization);
	specializeShaderSources(dst, src, specParams, SHADERTYPE_GEOMETRY,					generateGeometrySpecialization);
	specializeShaderSources(dst, src, specParams, SHADERTYPE_TESSELLATION_CONTROL,		generateTessControlSpecialization);
	specializeShaderSources(dst, src, specParams, SHADERTYPE_TESSELLATION_EVALUATION,	generateTessEvalSpecialization);

	dst << ProgramSeparable(src.separable);
}

enum
{
	VIEWPORT_WIDTH		= 128,
	VIEWPORT_HEIGHT		= 128
};

class BeforeDrawValidator : public glu::DrawUtilCallback
{
public:
	enum TargetType
	{
		TARGETTYPE_PROGRAM = 0,
		TARGETTYPE_PIPELINE,

		TARGETTYPE_LAST
	};

							BeforeDrawValidator	(const glw::Functions& gl, glw::GLuint target, TargetType targetType);

	void					beforeDrawCall		(void);

	const std::string&		getInfoLog			(void) const;
	glw::GLint				getValidateStatus	(void) const;

private:
	const glw::Functions&	m_gl;
	const glw::GLuint		m_target;
	const TargetType		m_targetType;

	glw::GLint				m_validateStatus;
	std::string				m_logMessage;
};

BeforeDrawValidator::BeforeDrawValidator (const glw::Functions& gl, glw::GLuint target, TargetType targetType)
	: m_gl				(gl)
	, m_target			(target)
	, m_targetType		(targetType)
	, m_validateStatus	(-1)
{
	DE_ASSERT(targetType < TARGETTYPE_LAST);
}

void BeforeDrawValidator::beforeDrawCall (void)
{
	glw::GLint					bytesWritten	= 0;
	glw::GLint					infoLogLength;
	std::vector<glw::GLchar>	logBuffer;
	int							stringLength;

	// validate
	if (m_targetType == TARGETTYPE_PROGRAM)
		m_gl.validateProgram(m_target);
	else if (m_targetType == TARGETTYPE_PIPELINE)
		m_gl.validateProgramPipeline(m_target);
	else
		DE_ASSERT(false);

	GLU_EXPECT_NO_ERROR(m_gl.getError(), "validate");

	// check status
	m_validateStatus = -1;

	if (m_targetType == TARGETTYPE_PROGRAM)
		m_gl.getProgramiv(m_target, GL_VALIDATE_STATUS, &m_validateStatus);
	else if (m_targetType == TARGETTYPE_PIPELINE)
		m_gl.getProgramPipelineiv(m_target, GL_VALIDATE_STATUS, &m_validateStatus);
	else
		DE_ASSERT(false);

	GLU_EXPECT_NO_ERROR(m_gl.getError(), "get validate status");
	TCU_CHECK(m_validateStatus == GL_TRUE || m_validateStatus == GL_FALSE);

	// read log

	infoLogLength = 0;

	if (m_targetType == TARGETTYPE_PROGRAM)
		m_gl.getProgramiv(m_target, GL_INFO_LOG_LENGTH, &infoLogLength);
	else if (m_targetType == TARGETTYPE_PIPELINE)
		m_gl.getProgramPipelineiv(m_target, GL_INFO_LOG_LENGTH, &infoLogLength);
	else
		DE_ASSERT(false);

	GLU_EXPECT_NO_ERROR(m_gl.getError(), "get info log length");

	if (infoLogLength <= 0)
	{
		m_logMessage.clear();
		return;
	}

	logBuffer.resize(infoLogLength + 2, '0'); // +1 for zero terminator (infoLogLength should include it, but better play it safe), +1 to make sure buffer is always larger

	if (m_targetType == TARGETTYPE_PROGRAM)
		m_gl.getProgramInfoLog(m_target, infoLogLength + 1, &bytesWritten, &logBuffer[0]);
	else if (m_targetType == TARGETTYPE_PIPELINE)
		m_gl.getProgramPipelineInfoLog(m_target, infoLogLength + 1, &bytesWritten, &logBuffer[0]);
	else
		DE_ASSERT(false);

	// just ignore bytesWritten to be safe, find the null terminator
	stringLength = (int)(std::find(logBuffer.begin(), logBuffer.end(), '0') - logBuffer.begin());
	m_logMessage.assign(&logBuffer[0], stringLength);
}

const std::string& BeforeDrawValidator::getInfoLog (void) const
{
	return m_logMessage;
}

glw::GLint BeforeDrawValidator::getValidateStatus (void) const
{
	return m_validateStatus;
}

// ShaderCase.

ShaderLibraryCase::ShaderLibraryCase (tcu::TestContext& testCtx, RenderContext& renderCtx, const glu::ContextInfo& contextInfo, const char* name, const char* description, const ShaderCaseSpecification& specification)
	: tcu::TestCase	(testCtx, name, description)
	, m_renderCtx	(renderCtx)
	, m_contextInfo	(contextInfo)
	, m_spec		(specification)
{
}

ShaderLibraryCase::~ShaderLibraryCase (void)
{
}

void ShaderLibraryCase::init (void)
{
	DE_ASSERT(isValid(m_spec));

	if (!isGLSLVersionSupported(m_renderCtx.getType(), m_spec.targetVersion))
		TCU_THROW(NotSupportedError, (string(getGLSLVersionName(m_spec.targetVersion)) + " is not supported").c_str());

	checkImplementationLimits(m_spec.requiredCaps, m_contextInfo);

	// log the expected result
	switch (m_spec.expectResult)
	{
		case EXPECT_PASS:
			// Don't write anything
			break;

		case EXPECT_COMPILE_FAIL:
			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting shader compilation to fail." << tcu::TestLog::EndMessage;
			break;

		case EXPECT_LINK_FAIL:
			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting program linking to fail." << tcu::TestLog::EndMessage;
			break;

		case EXPECT_COMPILE_LINK_FAIL:
			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting either shader compilation or program linking to fail." << tcu::TestLog::EndMessage;
			break;

		case EXPECT_VALIDATION_FAIL:
			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting program validation to fail." << tcu::TestLog::EndMessage;
			break;

		case EXPECT_BUILD_SUCCESSFUL:
			m_testCtx.getLog() << tcu::TestLog::Message << "Expecting shader compilation and program linking to succeed. Resulting program will not be executed." << tcu::TestLog::EndMessage;
			break;

		default:
			DE_ASSERT(false);
			break;
	}
}

static void setUniformValue (const glw::Functions& gl, const std::vector<deUint32>& pipelinePrograms, const std::string& name, const Value& val, int arrayNdx, tcu::TestLog& log)
{
	bool foundAnyMatch = false;

	for (int programNdx = 0; programNdx < (int)pipelinePrograms.size(); ++programNdx)
	{
		const DataType	dataType	= val.type.getBasicType();
		const int		scalarSize	= getDataTypeScalarSize(dataType);
		const int		loc			= gl.getUniformLocation(pipelinePrograms[programNdx], name.c_str());
		const int		elemNdx		= arrayNdx * scalarSize;

		DE_ASSERT(elemNdx+scalarSize <= (int)val.elements.size());

		if (loc == -1)
			continue;

		foundAnyMatch = true;

		DE_STATIC_ASSERT(sizeof(Value::Element) == sizeof(glw::GLfloat));
		DE_STATIC_ASSERT(sizeof(Value::Element) == sizeof(glw::GLint));

		gl.useProgram(pipelinePrograms[programNdx]);

		switch (dataType)
		{
			case TYPE_FLOAT:		gl.uniform1fv(loc, 1, &val.elements[elemNdx].float32);						break;
			case TYPE_FLOAT_VEC2:	gl.uniform2fv(loc, 1, &val.elements[elemNdx].float32);						break;
			case TYPE_FLOAT_VEC3:	gl.uniform3fv(loc, 1, &val.elements[elemNdx].float32);						break;
			case TYPE_FLOAT_VEC4:	gl.uniform4fv(loc, 1, &val.elements[elemNdx].float32);						break;
			case TYPE_FLOAT_MAT2:	gl.uniformMatrix2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);		break;
			case TYPE_FLOAT_MAT3:	gl.uniformMatrix3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);		break;
			case TYPE_FLOAT_MAT4:	gl.uniformMatrix4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);		break;
			case TYPE_INT:			gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_INT_VEC2:		gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_INT_VEC3:		gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_INT_VEC4:		gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_BOOL:			gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_BOOL_VEC2:	gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_BOOL_VEC3:	gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_BOOL_VEC4:	gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32);						break;
			case TYPE_UINT:			gl.uniform1uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
			case TYPE_UINT_VEC2:	gl.uniform2uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
			case TYPE_UINT_VEC3:	gl.uniform3uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
			case TYPE_UINT_VEC4:	gl.uniform4uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);		break;
			case TYPE_FLOAT_MAT2X3:	gl.uniformMatrix2x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
			case TYPE_FLOAT_MAT2X4:	gl.uniformMatrix2x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
			case TYPE_FLOAT_MAT3X2:	gl.uniformMatrix3x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
			case TYPE_FLOAT_MAT3X4:	gl.uniformMatrix3x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
			case TYPE_FLOAT_MAT4X2:	gl.uniformMatrix4x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;
			case TYPE_FLOAT_MAT4X3:	gl.uniformMatrix4x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);	break;

			case TYPE_SAMPLER_2D:
			case TYPE_SAMPLER_CUBE:
				DE_FATAL("implement!");
				break;

			default:
				DE_ASSERT(false);
		}
	}

	if (!foundAnyMatch)
		log << tcu::TestLog::Message << "WARNING // Uniform \"" << name << "\" location is not valid, location = -1. Cannot set value to the uniform." << tcu::TestLog::EndMessage;
}

static bool isTessellationPresent (const ShaderCaseSpecification& spec)
{
	if (spec.programs[0].sources.separable)
	{
		const deUint32 tessellationBits =	(1 << glu::SHADERTYPE_TESSELLATION_CONTROL)		|
											(1 << glu::SHADERTYPE_TESSELLATION_EVALUATION);

		for (int programNdx = 0; programNdx < (int)spec.programs.size(); ++programNdx)
			if (spec.programs[programNdx].activeStages & tessellationBits)
				return true;
		return false;
	}
	else
		return !spec.programs[0].sources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].empty() ||
			   !spec.programs[0].sources.sources[glu::SHADERTYPE_TESSELLATION_EVALUATION].empty();
}

static bool isTessellationSupported (const glu::RenderContext& renderCtx, const glu::ContextInfo& ctxInfo)
{
	if (renderCtx.getType().getProfile() == PROFILE_ES)
	{
		const int	majorVer	= renderCtx.getType().getMajorVersion();
		const int	minorVer	= renderCtx.getType().getMinorVersion();

		return (majorVer > 3) || (majorVer == 3 && minorVer >= 2) ||
			   ctxInfo.isExtensionSupported("GL_EXT_tessellation_shader");
	}
	else
		return false;
}

static bool checkPixels (tcu::TestLog& log, const tcu::ConstPixelBufferAccess& surface)
{
	bool	allWhite		= true;
	bool	allBlack		= true;
	bool	anyUnexpected	= false;

	for (int y = 0; y < surface.getHeight(); y++)
	{
		for (int x = 0; x < surface.getWidth(); x++)
		{
			const tcu::IVec4	pixel		 = surface.getPixelInt(x, y);
			// Note: we really do not want to involve alpha in the check comparison
			// \todo [2010-09-22 kalle] Do we know that alpha would be one? If yes, could use color constants white and black.
			const bool			isWhite		 = (pixel[0] == 255) && (pixel[1] == 255) && (pixel[2] == 255);
			const bool			isBlack		 = (pixel[0] ==   0) && (pixel[1] ==   0) && (pixel[2] ==   0);

			allWhite		= allWhite && isWhite;
			allBlack		= allBlack && isBlack;
			anyUnexpected	= anyUnexpected || (!isWhite && !isBlack);
		}
	}

	if (!allWhite)
	{
		if (anyUnexpected)
			log << TestLog::Message << "WARNING: expecting all rendered pixels to be white or black, but got other colors as well!" << TestLog::EndMessage;
		else if (!allBlack)
			log << TestLog::Message << "WARNING: got inconsistent results over the image, when all pixels should be the same color!" << TestLog::EndMessage;

		return false;
	}

	return true;
}

bool ShaderLibraryCase::execute (void)
{
	const float							quadSize				= 1.0f;
	static const float					s_positions[4*4]		=
	{
		-quadSize, -quadSize, 0.0f, 1.0f,
		-quadSize, +quadSize, 0.0f, 1.0f,
		+quadSize, -quadSize, 0.0f, 1.0f,
		+quadSize, +quadSize, 0.0f, 1.0f
	};

	static const deUint16				s_indices[2*3]			=
	{
		0, 1, 2,
		1, 3, 2
	};

	TestLog&							log						= m_testCtx.getLog();
	const glw::Functions&				gl						= m_renderCtx.getFunctions();

	// Compute viewport.
	const tcu::RenderTarget&			renderTarget			= m_renderCtx.getRenderTarget();
	de::Random							rnd						(deStringHash(getName()));
	const int							width					= deMin32(renderTarget.getWidth(),	VIEWPORT_WIDTH);
	const int							height					= deMin32(renderTarget.getHeight(),	VIEWPORT_HEIGHT);
	const int							viewportX				= rnd.getInt(0, renderTarget.getWidth()  - width);
	const int							viewportY				= rnd.getInt(0, renderTarget.getHeight() - height);
	const int							numVerticesPerDraw		= 4;
	const bool							tessellationPresent		= isTessellationPresent(m_spec);
	const bool							separablePrograms		= m_spec.programs[0].sources.separable;

	bool								allCompilesOk			= true;
	bool								allLinksOk				= true;
	const char*							failReason				= DE_NULL;

	vector<ProgramSources>				specializedSources		(m_spec.programs.size());

	deUint32							vertexProgramID			= -1;
	vector<deUint32>					pipelineProgramIDs;
	vector<SharedPtr<ShaderProgram> >	programs;
	SharedPtr<ProgramPipeline>			programPipeline;

	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): start");

	if(isCapabilityRequired(CAPABILITY_ONLY_GLSL_ES_100_SUPPORT, m_spec) && glu::IsES3Compatible(gl))
		return true;

	if(isCapabilityRequired(CAPABILITY_EXACTLY_ONE_DRAW_BUFFER, m_spec))
	{
		// on unextended ES2 there is only one draw buffer
		// and there is no GL_MAX_DRAW_BUFFERS query
		glw::GLint maxDrawBuffers = 0;
		gl.getIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
		if ((gl.getError() == GL_NO_ERROR) && (maxDrawBuffers > 1))
			throw tcu::NotSupportedError("Test requires exactly one draw buffer");
	}

	// Specialize shaders
	if (m_spec.caseType == CASETYPE_VERTEX_ONLY)
	{
		const vector<RequiredExtension>	reqExt	= checkAndSpecializeExtensions(m_spec.programs[0].requiredExtensions, m_contextInfo);

		DE_ASSERT(m_spec.programs.size() == 1 && m_spec.programs[0].sources.sources[SHADERTYPE_VERTEX].size() == 1);
		specializedSources[0] << glu::VertexSource(specializeVertexShader(m_spec, m_spec.programs[0].sources.sources[SHADERTYPE_VERTEX][0], reqExt))
							  << glu::FragmentSource(genFragmentShader(m_spec));
	}
	else if (m_spec.caseType == CASETYPE_FRAGMENT_ONLY)
	{
		const vector<RequiredExtension>	reqExt	= checkAndSpecializeExtensions(m_spec.programs[0].requiredExtensions, m_contextInfo);

		DE_ASSERT(m_spec.programs.size() == 1 && m_spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT].size() == 1);
		specializedSources[0] << glu::VertexSource(genVertexShader(m_spec))
							  << glu::FragmentSource(specializeFragmentShader(m_spec, m_spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT][0], reqExt));
	}
	else
	{
		DE_ASSERT(m_spec.caseType == CASETYPE_COMPLETE);

		const int	maxPatchVertices	= isTessellationPresent(m_spec) && isTessellationSupported(m_renderCtx, m_contextInfo)
										? m_contextInfo.getInt(GL_MAX_PATCH_VERTICES) : 0;

		for (size_t progNdx = 0; progNdx < m_spec.programs.size(); progNdx++)
		{
			const ProgramSpecializationParams	progSpecParams	(m_spec, checkAndSpecializeExtensions(m_spec.programs[progNdx].requiredExtensions, m_contextInfo), maxPatchVertices);

			specializeProgramSources(specializedSources[progNdx], m_spec.programs[progNdx].sources, progSpecParams);
		}
	}

	if (!separablePrograms)
	{
		de::SharedPtr<glu::ShaderProgram>	program		(new glu::ShaderProgram(m_renderCtx, specializedSources[0]));

		vertexProgramID = program->getProgram();
		pipelineProgramIDs.push_back(program->getProgram());
		programs.push_back(program);

		// Check that compile/link results are what we expect.

		DE_STATIC_ASSERT(glu::SHADERTYPE_VERTEX == 0);
		for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
			if (program->hasShader((glu::ShaderType)stage) && !program->getShaderInfo((glu::ShaderType)stage).compileOk)
				allCompilesOk = false;

		if (!program->getProgramInfo().linkOk)
			allLinksOk = false;

		log << *program;
	}
	else
	{
		// Separate programs
		for (size_t programNdx = 0; programNdx < m_spec.programs.size(); ++programNdx)
		{
			de::SharedPtr<glu::ShaderProgram> program(new glu::ShaderProgram(m_renderCtx, specializedSources[programNdx]));

			if (m_spec.programs[programNdx].activeStages & (1u << glu::SHADERTYPE_VERTEX))
				vertexProgramID = program->getProgram();

			pipelineProgramIDs.push_back(program->getProgram());
			programs.push_back(program);

			// Check that compile/link results are what we expect.

			DE_STATIC_ASSERT(glu::SHADERTYPE_VERTEX == 0);
			for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
				if (program->hasShader((glu::ShaderType)stage) && !program->getShaderInfo((glu::ShaderType)stage).compileOk)
					allCompilesOk = false;

			if (!program->getProgramInfo().linkOk)
				allLinksOk = false;

			// Log program and active stages
			{
				const tcu::ScopedLogSection	section		(log, "Program", "Program " + de::toString(programNdx+1));
				tcu::MessageBuilder			builder		(&log);
				bool						firstStage	= true;

				builder << "Pipeline uses stages: ";
				for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
				{
					if (m_spec.programs[programNdx].activeStages & (1u << stage))
					{
						if (!firstStage)
							builder << ", ";
						builder << glu::getShaderTypeName((glu::ShaderType)stage);
						firstStage = true;
					}
				}
				builder << tcu::TestLog::EndMessage;

				log << *program;
			}
		}
	}

	switch (m_spec.expectResult)
	{
		case EXPECT_PASS:
		case EXPECT_VALIDATION_FAIL:
		case EXPECT_BUILD_SUCCESSFUL:
			if (!allCompilesOk)
				failReason = "expected shaders to compile and link properly, but failed to compile.";
			else if (!allLinksOk)
				failReason = "expected shaders to compile and link properly, but failed to link.";
			break;

		case EXPECT_COMPILE_FAIL:
			if (allCompilesOk && !allLinksOk)
				failReason = "expected compilation to fail, but shaders compiled and link failed.";
			else if (allCompilesOk)
				failReason = "expected compilation to fail, but shaders compiled correctly.";
			break;

		case EXPECT_LINK_FAIL:
			if (!allCompilesOk)
				failReason = "expected linking to fail, but unable to compile.";
			else if (allLinksOk)
				failReason = "expected linking to fail, but passed.";
			break;

		case EXPECT_COMPILE_LINK_FAIL:
			if (allCompilesOk && allLinksOk)
				failReason = "expected compile or link to fail, but passed.";
			break;

		default:
			DE_ASSERT(false);
			return false;
	}

	if (failReason != DE_NULL)
	{
		// \todo [2010-06-07 petri] These should be handled in the test case?
		log << TestLog::Message << "ERROR: " << failReason << TestLog::EndMessage;

		if (isCapabilityRequired(CAPABILITY_FULL_GLSL_ES_100_SUPPORT, m_spec))
		{
			log	<< TestLog::Message
				<< "Assuming build failure is caused by implementation not supporting full GLSL ES 100 specification, which is not required."
				<< TestLog::EndMessage;

			if (allCompilesOk && !allLinksOk)
			{
				// Used features are detectable at compile time. If implementation parses shader
				// at link time, report it as quality warning.
				m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, failReason);
			}
			else
				m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Full GLSL ES 100 is not supported");
		}
		else if (m_spec.expectResult == EXPECT_COMPILE_FAIL && allCompilesOk && !allLinksOk)
		{
			// If implementation parses shader at link time, report it as quality warning.
			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, failReason);
		}
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failReason);
		return false;
	}

	// Return if shader is not intended to be run
	if (m_spec.expectResult == EXPECT_COMPILE_FAIL		||
		m_spec.expectResult == EXPECT_COMPILE_LINK_FAIL	||
		m_spec.expectResult == EXPECT_LINK_FAIL			||
		m_spec.expectResult == EXPECT_BUILD_SUCCESSFUL)
		return true;

	// Setup viewport.
	gl.viewport(viewportX, viewportY, width, height);

	if (separablePrograms)
	{
		programPipeline = de::SharedPtr<glu::ProgramPipeline>(new glu::ProgramPipeline(m_renderCtx));

		// Setup pipeline
		gl.bindProgramPipeline(programPipeline->getPipeline());
		for (int programNdx = 0; programNdx < (int)m_spec.programs.size(); ++programNdx)
		{
			deUint32 shaderFlags = 0;
			for (int stage = glu::SHADERTYPE_VERTEX; stage < glu::SHADERTYPE_LAST; ++stage)
				if (m_spec.programs[programNdx].activeStages & (1u << stage))
					shaderFlags |= glu::getGLShaderTypeBit((glu::ShaderType)stage);

			programPipeline->useProgramStages(shaderFlags, pipelineProgramIDs[programNdx]);
		}

		programPipeline->activeShaderProgram(vertexProgramID);
		GLU_EXPECT_NO_ERROR(gl.getError(), "setup pipeline");
	}
	else
	{
		// Start using program
		gl.useProgram(vertexProgramID);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");
	}

	// Fetch location for positions positions.
	int positionLoc = gl.getAttribLocation(vertexProgramID, "dEQP_Position");
	if (positionLoc == -1)
	{
		string errStr = string("no location found for attribute 'dEQP_Position'");
		TCU_FAIL(errStr.c_str());
	}

	// Iterate all value blocks.
	{
		const ValueBlock&	valueBlock		= m_spec.values;

		// always render at least one pass even if there is no input/output data
		const int			numRenderPasses	= valueBlock.outputs.empty() ? 1 : (int)valueBlock.outputs[0].elements.size() / valueBlock.outputs[0].type.getScalarSize();

		// Iterate all array sub-cases.
		for (int arrayNdx = 0; arrayNdx < numRenderPasses; arrayNdx++)
		{
			vector<VertexArrayBinding>	vertexArrays;
			int							attribValueNdx		= 0;
			vector<vector<float> >		attribValues		(valueBlock.inputs.size());
			glw::GLenum					postDrawError;
			BeforeDrawValidator			beforeDrawValidator	(gl,
															 (separablePrograms) ? (programPipeline->getPipeline())			: (vertexProgramID),
															 (separablePrograms) ? (BeforeDrawValidator::TARGETTYPE_PIPELINE)	: (BeforeDrawValidator::TARGETTYPE_PROGRAM));

			vertexArrays.push_back(va::Float(positionLoc, 4, numVerticesPerDraw, 0, &s_positions[0]));

			// Collect VA pointer for inputs
			for (size_t valNdx = 0; valNdx < valueBlock.inputs.size(); valNdx++)
			{
				const Value&		val			= valueBlock.inputs[valNdx];
				const char* const	valueName	= val.name.c_str();
				const DataType		dataType	= val.type.getBasicType();
				const int			scalarSize	= getDataTypeScalarSize(dataType);

				// Replicate values four times.
				std::vector<float>& scalars = attribValues[attribValueNdx++];
				scalars.resize(numVerticesPerDraw * scalarSize);
				if (isDataTypeFloatOrVec(dataType) || isDataTypeMatrix(dataType))
				{
					for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++)
						for (int ndx = 0; ndx < scalarSize; ndx++)
							scalars[repNdx*scalarSize + ndx] = val.elements[arrayNdx*scalarSize + ndx].float32;
				}
				else
				{
					// convert to floats.
					for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++)
					{
						for (int ndx = 0; ndx < scalarSize; ndx++)
						{
							float v = (float)val.elements[arrayNdx*scalarSize + ndx].int32;
							DE_ASSERT(val.elements[arrayNdx*scalarSize + ndx].int32 == (int)v);
							scalars[repNdx*scalarSize + ndx] = v;
						}
					}
				}

				// Attribute name prefix.
				string attribPrefix = "";
				// \todo [2010-05-27 petri] Should latter condition only apply for vertex cases (or actually non-fragment cases)?
				if ((m_spec.caseType == CASETYPE_FRAGMENT_ONLY) || (getDataTypeScalarType(dataType) != TYPE_FLOAT))
					attribPrefix = "a_";

				// Input always given as attribute.
				string attribName = attribPrefix + valueName;
				int attribLoc = gl.getAttribLocation(vertexProgramID, attribName.c_str());
				if (attribLoc == -1)
				{
					log << TestLog::Message << "Warning: no location found for attribute '" << attribName << "'" << TestLog::EndMessage;
					continue;
				}

				if (isDataTypeMatrix(dataType))
				{
					int numCols = getDataTypeMatrixNumColumns(dataType);
					int numRows = getDataTypeMatrixNumRows(dataType);
					DE_ASSERT(scalarSize == numCols*numRows);

					for (int i = 0; i < numCols; i++)
						vertexArrays.push_back(va::Float(attribLoc + i, numRows, numVerticesPerDraw, scalarSize*(int)sizeof(float), &scalars[i * numRows]));
				}
				else
				{
					DE_ASSERT(isDataTypeFloatOrVec(dataType) || isDataTypeIntOrIVec(dataType) || isDataTypeUintOrUVec(dataType) || isDataTypeBoolOrBVec(dataType));
					vertexArrays.push_back(va::Float(attribLoc, scalarSize, numVerticesPerDraw, 0, &scalars[0]));
				}

				GLU_EXPECT_NO_ERROR(gl.getError(), "set vertex attrib array");
			}

			GLU_EXPECT_NO_ERROR(gl.getError(), "before set uniforms");

			// set reference values for outputs.
			for (size_t valNdx = 0; valNdx < valueBlock.outputs.size(); valNdx++)
			{
				const Value&		val			= valueBlock.outputs[valNdx];
				const char* const	valueName	= val.name.c_str();

				// Set reference value.
				string refName = string("ref_") + valueName;
				setUniformValue(gl, pipelineProgramIDs, refName, val, arrayNdx, m_testCtx.getLog());
				GLU_EXPECT_NO_ERROR(gl.getError(), "set reference uniforms");
			}

			// set uniform values
			for (size_t valNdx = 0; valNdx < valueBlock.uniforms.size(); valNdx++)
			{
				const Value&		val			= valueBlock.uniforms[valNdx];
				const char* const	valueName	= val.name.c_str();

				setUniformValue(gl, pipelineProgramIDs, valueName, val, arrayNdx, m_testCtx.getLog());
				GLU_EXPECT_NO_ERROR(gl.getError(), "set uniforms");
			}

			// Clear.
			gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
			gl.clear(GL_COLOR_BUFFER_BIT);
			GLU_EXPECT_NO_ERROR(gl.getError(), "clear buffer");

			// Use program or pipeline
			if (separablePrograms)
				gl.useProgram(0);
			else
				gl.useProgram(vertexProgramID);

			// Draw.
			if (tessellationPresent)
			{
				gl.patchParameteri(GL_PATCH_VERTICES, 3);
				GLU_EXPECT_NO_ERROR(gl.getError(), "set patchParameteri(PATCH_VERTICES, 3)");
			}

			draw(m_renderCtx,
				 vertexProgramID,
				 (int)vertexArrays.size(),
				 &vertexArrays[0],
				 (tessellationPresent) ?
					(pr::Patches(DE_LENGTH_OF_ARRAY(s_indices), &s_indices[0])) :
					(pr::Triangles(DE_LENGTH_OF_ARRAY(s_indices), &s_indices[0])),
				 (m_spec.expectResult == EXPECT_VALIDATION_FAIL) ?
					(&beforeDrawValidator) :
					(DE_NULL));

			postDrawError = gl.getError();

			if (m_spec.expectResult == EXPECT_PASS)
			{
				// Read back results.
				Surface			surface			(width, height);
				const float		w				= s_positions[3];
				const int		minY			= deCeilFloatToInt32 (((-quadSize / w) * 0.5f + 0.5f) * (float)height + 1.0f);
				const int		maxY			= deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * (float)height - 0.5f);
				const int		minX			= deCeilFloatToInt32 (((-quadSize / w) * 0.5f + 0.5f) * (float)width + 1.0f);
				const int		maxX			= deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * (float)width - 0.5f);

				GLU_EXPECT_NO_ERROR(postDrawError, "draw");

				glu::readPixels(m_renderCtx, viewportX, viewportY, surface.getAccess());
				GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

				if (!checkPixels(log, tcu::getSubregion(surface.getAccess(), minX, minY, maxX-minX+1, maxY-minY+1)))
				{
					log << TestLog::Message << "INCORRECT RESULT for sub-case " << arrayNdx+1 << " of " << numRenderPasses << "):"
						<< TestLog::EndMessage;

					log << TestLog::Message << "Failing shader input/output values:" << TestLog::EndMessage;
					dumpValues(log, valueBlock, arrayNdx);

					// Dump image on failure.
					log << TestLog::Image("Result", "Rendered result image", surface);

					gl.useProgram(0);
					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
					return false;
				}
			}
			else if (m_spec.expectResult == EXPECT_VALIDATION_FAIL)
			{
				log	<< TestLog::Message
					<< "Draw call generated error: "
					<< glu::getErrorStr(postDrawError) << " "
					<< ((postDrawError == GL_INVALID_OPERATION) ? ("(expected)") : ("(unexpected)")) << "\n"
					<< "Validate status: "
					<< glu::getBooleanStr(beforeDrawValidator.getValidateStatus()) << " "
					<< ((beforeDrawValidator.getValidateStatus() == GL_FALSE) ? ("(expected)") : ("(unexpected)")) << "\n"
					<< "Info log: "
					<< ((beforeDrawValidator.getInfoLog().empty()) ? ("[empty string]") : (beforeDrawValidator.getInfoLog())) << "\n"
					<< TestLog::EndMessage;

				// test result

				if (postDrawError != GL_NO_ERROR && postDrawError != GL_INVALID_OPERATION)
				{
					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, ("Draw: got unexpected error: " + de::toString(glu::getErrorStr(postDrawError))).c_str());
					return false;
				}

				if (beforeDrawValidator.getValidateStatus() == GL_TRUE)
				{
					if (postDrawError == GL_NO_ERROR)
						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but validation and rendering succeeded");
					else if (postDrawError == GL_INVALID_OPERATION)
						m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but validation succeeded (rendering failed as expected)");
					else
						DE_ASSERT(false);
					return false;
				}
				else if (beforeDrawValidator.getValidateStatus() == GL_FALSE && postDrawError == GL_NO_ERROR)
				{
					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "expected validation and rendering to fail but rendering succeeded (validation failed as expected)");
					return false;
				}
				else if (beforeDrawValidator.getValidateStatus() == GL_FALSE && postDrawError == GL_INVALID_OPERATION)
				{
					// Validation does not depend on input values, no need to test all values
					return true;
				}
				else
					DE_ASSERT(false);
			}
			else
				DE_ASSERT(false);
		}
	}

	gl.useProgram(0);
	if (separablePrograms)
		gl.bindProgramPipeline(0);

	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): end");
	return true;
}

TestCase::IterateResult ShaderLibraryCase::iterate (void)
{
	// Initialize state to pass.
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	bool executeOk = execute();

	DE_ASSERT(executeOk ? m_testCtx.getTestResult() == QP_TEST_RESULT_PASS : m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
	DE_UNREF(executeOk);
	return TestCase::STOP;
}

} // gls
} // deqp
